#!/usr/bin/python





import copy
import pickle
import os
from exceptions import Exception
import threading

from ontology_manager import OntologyManager

from followed_thread import FollowedThread



# page objects
OTHERS_HINT = document.getElementById("othersHint")
OTHERS_LIST = document.getElementById("othersList")
OTHERS_VALUE = document.getElementById("othersValue")
OTHERS_AREOBJECTS = document.getElementById("othersAreObjects")
OTHERS_MORE = document.getElementById("btMore")
OTHERS_FILTER = document.getElementById("othersFilter")
OTHERS_PROGRESS = document.getElementById("progressOthers")

CURRENT_THING_NAME = document.getElementById("currentThingName")
PROPERTIES_FILTER = document.getElementById("propsFilter")
PROPERTIES_LIST = document.getElementById("propertiesList")
LABEL_PAGE_URI = document.getElementById("labelPageURL")
PROPS_FILTER = document.getElementById("propsFilter")

URI_DISPLAY = document.getElementById("uri_display")


# constants
NO_EXISTING_NAME = "No existing name, enter one."
PRISTINE_PROPS_FILTER = "Type your filter here..."
PROPERTIES_FILTER.setAttribute("value", PRISTINE_PROPS_FILTER)

PRISTINE_OTHERS_FILTER = "Search element by name..."
OTHERS_FILTER.setAttribute("value", PRISTINE_OTHERS_FILTER)

THREAD_PROPS = "PropertiesListClick"

OTHERS_MODE_PROPERTY = 0
OTHERS_MODE_ELTS_SEARCH = 1

logger = logging.getLogger("xul_pimo")



"""
~~~~~~~~~~~~~~~~~~~~ DOCUMENT ATTRIBUTES ~~~~~~~~~~~~~~~~~~~
These attributes are set at page load (onLoad() method and used in the whole page.

- document.NEPO : TheNepomukWhisperer
	* Used to call all the Nepomuk methods, through document.NEPO.interface|nao|pimo etc.

- document.page_thing_name : String
	* Used to remember the name found at the page load. Useful to detect if the user has changed the page name, and then register the possible change in Nepomuk.

- document.page_uri : String
	* Used to remember the page URI.

- document.properties : list([string(name), string(uri)])
	* Used to contain the current list of properties to be displayed in the XUL:listbox.

- document.current_property_uri : String
	* The property of which the possible range elements are currently displayed.

- document.refresh_props_list_timeout_id
	* The current value that identifies the window timeout used to refresh the props list.

- document.others : list([string(name), string(uri)])
	* Used to contain the current list of elements to be displayed in the XUL:listbox.

- document.others_priority_level : Integer
	* Used to set at which priority level the others list is built.

- document.others_mode : DOCUMENT_MODE (see possible modes in constants of this page)

- document.thread_props : the thread currently running (if it exists) which gets the other elements to display.

"""

document.NEPO = None
document.page_uri = None

document.properties = list()
document.current_property_uri = None
document.refresh_props_list_timeout_id = None

document.others = list()
document.others_priority_level = None

document.mode = OTHERS_MODE_PROPERTY

document.thread_props = None




############################################################

def onLoad():
	
	try:
		watchedOnLoad()
	except Exception, e:
		PROMPTS.alert(window, "Error", "Problem accessing the needed elements to use Nepomuk Foxtrot. Please check that nepomukserver is up and running (Error is \"%s\")." % e)
		window.close()

############################################################
	
def watchedOnLoad():
	
	logger.debug("onLoad()")
	
	document.page_uri = window.opener.content.document.location.href
	
	# attach TheNepomukWhisperer to the document
	from the_nepomuk_whisperer import TheNepomukWhisperer
	document.NEPO = TheNepomukWhisperer(ONTOLOGIES_STORAGE_FILE)
	
	# Display the URL of the web page
	LABEL_PAGE_URI.setAttribute("value", document.page_uri)
	
	
	# set the page thing name
	document.page_thing_name = document.NEPO.pimo.getName(document.page_uri)
	if document.page_thing_name == None:
		CURRENT_THING_NAME.value = NO_EXISTING_NAME
	else:
		CURRENT_THING_NAME.value = document.page_thing_name

############################################################

def onFocus():
	
	logger.debug("onFocus()")
	
	ontos_mgr = OntologyManager(document.NEPO.interface, ONTOLOGIES_STORAGE_FILE)
	ontologiesList = ontos_mgr.getSelectedOntologies()
	# Load the propertiesList.
	named_properties = document.NEPO.pimo.getAllPropertiesOf(ontologiesList)
	
	# clear the props list
	document.properties = list()
	
	# clear the XUL properties list
	while PROPERTIES_LIST.childNodes.length != 0:
		PROPERTIES_LIST.removeChild(PROPERTIES_LIST.firstChild)
	
	# add all the retrieved properties in properties
	for named_property in named_properties:
		document.properties.append([named_property[0], named_property[1]])
		PROPERTIES_LIST.appendItem(named_property[0], named_property[1])
	
	logger.debug("nb properties = "+str(len(document.properties)))
	

############################################################

# This is an ugly method which allows to look for a thread ending. This could be far better implemented with a threads-signals-notify-etc. system that is actually available in Python. The problem is that the DOM is not thread safe, so it cannot be accessed by a thread without high and random risk of crash...
def refreshOthersList():
	
	logger.debug("***refreshOthersList()***")
	
	# Check whether THREAD_PROPS is still running.
	threads = threading.enumerate()
	for thread in threads:
		if thread.getName() == THREAD_PROPS:
			document.refresh_props_list_timeout_id = window.setTimeout(500, refreshOthersList)
			return
	logger.debug(THREAD_PROPS + " has stopped running")
	
	endingMethod()

############################################################

def endingMethodForClick():
	
	requestTerminated()
	# add the elements in the XUL list
	for named_elt in document.others:
		OTHERS_LIST.appendItem(named_elt[0], named_elt[1])

############################################################

def endingMethodForKeyUp():
	
	requestStopped()
	# add the elements in the XUL list
	for named_elt in document.others:
		OTHERS_LIST.appendItem(named_elt[0], named_elt[1])

############################################################

def beginningMethod():
	
	# Clear the list of other possible elements
	while OTHERS_LIST.childNodes.length != 0:
		OTHERS_LIST.removeChild(OTHERS_LIST.firstChild)
	
	document.current_property_uri = PROPERTIES_LIST.selectedItem.getAttribute("value")
	#logger.debug("property_uri = "+property_uri)
	
	if document.NEPO.pimo.isDescribingProperty(document.current_property_uri):
		logger.debug("describingProperty")
		othersAreValues()
		return False
	else:
		logger.debug("non-describingProperty")
		othersAreObjects()
		othersMode(OTHERS_MODE_PROPERTY)
		return True

############################################################

def propertiesListClick(event):
	
	# TODO: allow the display of others_list with any button to maintain coherence between properties_list.selectedItem and the elements displayed in others_list (do this only when there are threads!)
	if event.button != 0: # not a left-click
		return 0
	
	if PROPERTIES_LIST.selectedIndex == -1:
		return 1
	
	URI_DISPLAY.setAttribute("value", PROPERTIES_LIST.selectedItem.value)
	
	document.others_priority_level = 0
	
	if document.thread_props != None:
		document.thread_props.kill()
	
	if beginningMethod(): # non-describing property
		requestRun()
		document.thread_props = FollowedThread(window, THREAD_PROPS, getOtherElements, (document.current_property_uri,), endingMethodForClick)
		document.thread_props.run()
	else: # describing property
		requestStopped()
		document.thread_props = None
		logger.debug("No need to start THREAD_PROPS.")

############################################################

def moreResults():
	
	if document.current_property_uri == None:
		return 1 # there is no current property
	
	if document.thread_props != None:
		document.thread_props.kill()
	
	othersMode(OTHERS_MODE_PROPERTY)
	
	requestRun()
	document.others_priority_level += 1
	document.thread_props = FollowedThread(window, THREAD_PROPS, getOtherElements, (document.current_property_uri,), endingMethodForClick)
	document.thread_props.run()

############################################################

# This method is meant to be called in a separate thread.
def getOtherElements(property_uri):
	
	logger.debug("getOtherElements(%s, %s)", property_uri, document.others_priority_level)
	
	document.others = list()
	
	ontologies = document.NEPO.ontos_mgr.getOntologiesAtLevel(document.others_priority_level)
	if ontologies == None:
		logger.debug("No more results!")
		return 1
	
	# Generate the list of other possible elements to relate
	while (True):
		
		named_elements = document.NEPO.pimo.getPossibleRangeElementsFor(property_uri, document.others_priority_level, ontologies)
		
		if named_elements == []:
			logger.debug("No new element found at level %s, moving on to next level." % document.others_priority_level)
			document.others_priority_level += 1
			getOtherElements(property_uri)
			return
		else:
			break
	
	if named_elements == None:
		logger.debug("No more results!") #PROMPTS.alert(window, "Information", "There are not any more results.") (not possible since the DOM is not thread safe!)
	else:
		logger.debug("named_elements = %s" % named_elements)
		for named_elt in named_elements:
			document.others.append([named_elt[0], named_elt[1]])

############################################################

def propsFilterFocus():
	
	logger.debug("focus")
	if PROPERTIES_FILTER.value == PRISTINE_PROPS_FILTER:
		PROPERTIES_FILTER.value = ""

############################################################

def propsFilterKeyUp():
	
	filter_string = PROPS_FILTER.value
	
	# Clear the properties list.
	while PROPERTIES_LIST.childNodes.length != 0:
		PROPERTIES_LIST.removeChild(PROPERTIES_LIST.firstChild)
	
	# Add properties that contains the filter text to the properties list (case insensitive).
	logger.debug("filter")
	#logger.debug("properties = "+str(len(properties)))
	
	for named_property in document.properties:
		i = named_property[1].lower().find(filter_string.lower())
		#logger.debug(named_property[0]+".lower().find("+filter_string+".lower())"+str(i))
		if i != -1:
			PROPERTIES_LIST.appendItem(named_property[0], named_property[1])

############################################################

def othersListClick():
	
	if OTHERS_LIST.selectedIndex == -1:
		return 1
	
	property_uri = OTHERS_LIST.selectedItem.getAttribute("value")
	URI_DISPLAY.setAttribute("value", property_uri)

############################################################

def othersFilterFocus():
	
	if OTHERS_FILTER.value == PRISTINE_OTHERS_FILTER:
		OTHERS_FILTER.value = ""

############################################################

def othersFilterKeyUp():
	
	if document.thread_props != None:
		document.thread_props.kill()
	
	othersMode(OTHERS_MODE_ELTS_SEARCH)
	
	# Clear the others list.
	while OTHERS_LIST.childNodes.length != 0:
		OTHERS_LIST.removeChild(OTHERS_LIST.firstChild)
	
	filter_string = OTHERS_FILTER.value
	if len(filter_string) < 2:
		document.thread_props = None
		requestStopped()
		return 0
	else:
		requestRun()
		document.thread_props = FollowedThread(window, THREAD_PROPS, getResourcesMatchingFilter, (filter_string,), endingMethodForKeyUp)
		document.thread_props.run()

############################################################

def getResourcesMatchingFilter(filter_string):
	
	document.others = []
	resources = document.NEPO.interface.getAllNonPropertyObjects(filter_string)
	logger.debug("len(resources) = %s" % len(resources))
	
	i = 0
	for resource in resources:
		if i > 20:
			break # for loop
		i += 1
		name = document.NEPO.pimo.getName(resource)
		document.others.append([name, resource])

############################################################

def delProp():
	
	property_name = PROPERTIES_LIST.selectedItem.getAttribute("label")
	answer = PROMPTS.confirm(window, "Confirmation", "Are you sure you want to delete the property named \"" + property_name + "\"?")
	
	if answer:
		deleteProperty()
	else:
		pass

############################################################

def deleteProperty():
	
	property_uri = PROPERTIES_LIST.selectedItem.getAttribute("value")
	property_name = PROPERTIES_LIST.selectedItem.getAttribute("label")
	logger.debug("property_uri="+property_uri)
	
	try:
		if document.NEPO.pimo.isDescribingProperty(property_uri):
			logger.debug("***	 describingProperty	 ***")
			document.NEPO.pimo.deleteDescribingProperty(property_uri)
		else:
			logger.debug("*** non-describingProperty ***")
			document.NEPO.pimo.deleteProperty(property_uri)
		
		PROMPTS.alert(window, "Information", "The property named \"" + property_name + "\" has properly been deleted.")
	
	except CanNotException, e:
		PROMPTS.alert(window, "Error", str(e))
	
############################################################

def othersAreObjects():
	
	OTHERS_HINT.setAttribute("value", "Other possible elements:")
	OTHERS_LIST.setAttribute("hidden", "false")
	OTHERS_MORE.setAttribute("hidden", "false")
	OTHERS_FILTER.setAttribute("hidden", "false")
	
	OTHERS_VALUE.setAttribute("hidden", "true")

############################################################

def othersAreValues():
	
	OTHERS_HINT.setAttribute("value", "Enter a value:")
	OTHERS_LIST.setAttribute("hidden", "true")
	OTHERS_MORE.setAttribute("hidden", "true")
	OTHERS_FILTER.setAttribute("hidden", "true")
	
	OTHERS_VALUE.setAttribute("hidden", "false")

############################################################

def requestRun():
	
	OTHERS_MORE.hidden = True
	OTHERS_MORE.disabled = True
	OTHERS_PROGRESS.hidden = False

############################################################

def requestStopped():
	
	OTHERS_PROGRESS.hidden = True

############################################################

def requestTerminated():
	
	requestStopped()
	
	OTHERS_MORE.hidden = False
	OTHERS_MORE.label = "More"
	
	# display information for the user to know what the next level (more button) is about.
	priority_level_description = document.NEPO.ontos_mgr.getPriorityLevelDescription(document.others_priority_level + 1)
	
	if priority_level_description != None:
		OTHERS_MORE.label = "More (search in %s)" % priority_level_description
		OTHERS_MORE.disabled = False
	else:
		OTHERS_MORE.label = "More (no more results)"
		OTHERS_MORE.disabled = True

############################################################

def othersMode(mode):
	
	document.mode = mode
	
	if mode == OTHERS_MODE_PROPERTY:
		name =  document.NEPO.pimo.getName(document.current_property_uri)
		val = "Elements compatible with relation \"%s\":" % name
		OTHERS_HINT.setAttribute("value", val)
		
	elif mode == OTHERS_MODE_ELTS_SEARCH:
		val = "Elements containing \"%s\":" % OTHERS_FILTER.value
		OTHERS_HINT.setAttribute("value", val)
		OTHERS_MORE.disabled = True
	
	else:
		logger.debug("OTHERS_HINT.value = ??? (%s is not known as a mode!)" % mode)

############################################################

def addRelation():
	
	# different checkings
	if (CURRENT_THING_NAME.value == NO_EXISTING_NAME) or (CURRENT_THING_NAME.value == ""):
		PROMPTS.alert(window, "Missing information", "Please enter a thing name for the current page.")
		return 1
	
	property_uri = None
	if PROPERTIES_LIST.selectedIndex != -1:
		property_uri = PROPERTIES_LIST.selectedItem.getAttribute("value")
		property_name = PROPERTIES_LIST.selectedItem.getAttribute("label")
	else:
		PROMPTS.alert(window, "Missing information", "Select a property from the list to relate your elements.")
		return 1
	
	try:
		if OTHERS_LIST.getAttribute("hidden") == "true":
			
			# valued description
			if OTHERS_VALUE.value == "":
				PROMPTS.alert(window, "Missing information", "Please enter a value to describe \"" + CURRENT_THING_NAME.value + "\" with the property \"" + property_name + "\".")
				return 1
			else:
				# register the name given for the current web page
				thing_uri = document.NEPO.interface.registerAndReify(document.page_uri, NFO_WEBSITE, CURRENT_THING_NAME.value)
				document.NEPO.pimo.setValuedDescription(thing_uri, property_uri, OTHERS_VALUE.value)
			
		else:
			
			# object-to-object relation
			object_uri = None
			if OTHERS_LIST.selectedIndex == -1:
				PROMPTS.alert(window, "Missing information", "Select an object from the objects list on the right to relate with \"" + CURRENT_THING_NAME.value + "\".")
				return 1
			else:
				# register the pimo:Thing name given for the current web page
				thing_uri = document.NEPO.interface.registerAndReify(document.page_uri, NFO_WEBSITE, CURRENT_THING_NAME.value)
				object_uri = OTHERS_LIST.selectedItem.getAttribute("value")
				document.NEPO.pimo.addRelation(thing_uri, property_uri, object_uri)
		
		PROMPTS.alert(window, "Information", "The relation has properly been added.")
		window.close()
	
	except CanNotException, e:
		PROMPTS.alert(window, "Missing information", str(e))
	
	#except TypeError, e:
		#PROMPTS.alert(window, "Bad type", str(e))

############################################################

def onUnload():
	
	logger.debug("onUnload()")
	if document.thread_props != None:
		document.thread_props.kill()





